package jupiter.messaging;
import java.io.Serializable;

import javax.jms.MessageListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.json.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.sql.ResultSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jupiter.filters.*;
import jupiter.util.DBConnection;
import jupiter.util.Timey;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.util.Enumeration;

public class Message implements Serializable{
	
	private static Pattern domainp = Pattern.compile("https?://(?:www\\.)?([^/]*)([^#\\?]*)");


	Logger logger = LoggerFactory.getLogger(Message.class);
	public String message;
	public String to;
	public String page;
	public String type;
	public String action;
	public String from;		//from call_id
	public String name;
	public String website;
	public JSONObject parsed;
	public String user_id;
	private boolean stillSend = false;
	private HttpServletRequest request;
	public boolean is_banned = false;
	public boolean read_only = false;
	public boolean is_registered = false;
	
	public Message(String m){
		super();
		message= m;
		try{
			parsed = new JSONObject(m);
			
			if(parsed.has("to")) to  = parsed.getString("to");
			if(parsed.has("user_id")) user_id = parsed.remove("user_id").toString();
			if(parsed.has("name")) name = parsed.getString("name");
			if(parsed.has("website")) website = parsed.getString("website");
			
			type  = parsed.getString("type");
			action = parsed.getString("action");
			from = parsed.getString("from"); 
			page = parsed.getString("page");
			if(page == null)
				page = "file.file";
		}catch(JSONException e){
			logger.error("JSONException in Message string constructor: "+m, e);
		}
		
	}
	/**
	 * This constructor is used only for the ServerMessage service
	 * @param req
	 */
	public Message(HttpServletRequest req){
		String mes = req.getParameter("message");
		name = req.getParameter("name");
		type = req.getParameter("type");
		action = req.getParameter("action");
		String key = req.getParameter("key");
		
		Enumeration<String> names = req.getParameterNames();
		if(req.getParameter("to") != null) to  = req.getParameter("to");
		page = getPageFromKey(key);
		if(page == null)
			page = "file.file";
		// mark as registered no matter what
		register();
		user_id = key;
		from = key;
		request = req;
		website = page;
		parsed = new JSONObject();
		try{
			if(to != null) parsed.put("to", to);
			parsed.put("name", name);
			parsed.put("type", type);
			parsed.put("action", action);
			parsed.put("page", page);
			parsed.put("from", from);
			parsed.put("user_id", from);
			parsed.put("website", website);
			parsed.put("message_push", true);
			String attr;
			while (names.hasMoreElements()){
				attr = names.nextElement();
				parsed.put(attr, req.getParameter(attr));
			}
			if(mes != null) parsed.put("message", mes);
			message = parsed.toString();
			logger.info("message: "+message);
		}catch(JSONException e){
			logger.error("JSONException in Message ServerMessage constructor", e);
		}
	}
	/**
	 * This constructor is used only for user.create messages
	 * @param m
	 * @param req
	 */
	public Message(String m, HttpServletRequest req){
		//first lets make sure this is USER
		message= m;
		try{
			String ip = null;
			ip = req.getHeader("X-Real-IP");
			if(ip == null)
				ip = req.getRemoteAddr();
//			logger.debug("sent by "+ip);
			
			
			parsed = new JSONObject(m);
			if(parsed.has("name")) name = parsed.getString("name");
			if(parsed.has("website")) website = parsed.getString("website");
			if(parsed.has("from")) from = parsed.getString("from");
			if(parsed.has("user_id")) user_id = (String) parsed.remove("user_id").toString();
			
			type  = parsed.getString("type");
			action  = parsed.getString("action");

			page = req.getHeader("Referer");
			if(page == null)
				page = "file.file";
			parsed.put("page", page);
			
			request = req;
		}catch(JSONException e){
			logger.error("JSONException in Message user.create constructor", e);
		}
		logger.info("new Message "+type+":"+action);
		
	}
	
	/**
	 * Most common constructor for client-initiated messages after they've connected.
	 * @param m
	 * @param i
	 * @param req
	 */
	public Message(String m, Inbox i, HttpServletRequest req) {
		super();
		message= m;
		try{
			parsed = new JSONObject(m);
			
			if(parsed.has("to")) to  = parsed.getString("to");
			is_banned = i.is_banned;
			is_registered = i.is_registered;
			type  = parsed.getString("type");
			action  = parsed.getString("action");
			from = i.call_id; 
			page = req.getHeader("Referer");
			read_only = i.read_only;
			logger.info("READ ONLY: "+read_only);
			if(page == null)
				page = "file.file";
			user_id = i.id;
			//add call id
			parsed.put("page", page);
			parsed.put("from", from);
			parsed.put("user_id", user_id);
			request = req;
			
			// add name and website so rails can save them for each message
			if(parsed.has("name")) name = parsed.getString("name");
			else name = i.name;
			if(parsed.has("website")) website = parsed.getString("website");
			else website = i.website;
			parsed.put("name", name);
			parsed.put("website", website);
		}catch(JSONException e){
			logger.error("JSONException in Message client-initiated constructor", e);
		}
		logger.info("new Message "+type+":"+action);
	}

	/**
	 * Runs message through filter.
	 * If filter returns nothing, this means message is OK.
	 * Then sends message to everyone after attaching a timestamp.
	 * Otherwise returns filter output without sending.
	 * @return
	 * @throws Exception 
	 */
	public JSONObject send() throws Exception{
		//go through filters
		Timey t = new Timey();
		JSONObject filterResults = filter();

		if(is_banned){
			to = from;
			parsed.put("to", to);
		}
		logger.info("filterResults: "+filterResults);
		if((filterResults != null && ! stillSend) || !is_registered){
			return filterResults;
		}
		logger.info(this.toString());
		try{
			this.parsed.put("timestamp", System.currentTimeMillis());
		}catch(JSONException e){
			logger.error("timestamp JSONException", e);
			logger.info(e.toString());
		}
		t.end("AfterFilter");
		subscriber.sendText(this.toString());
		t.end("AfterSend");
		
		return filterResults;
	}
	
	/**
	 * If this returns anything besides null, the message is not sent to everyone
	 * @return
	 * @throws Exception 
	 */
	public JSONObject filter() {
			// if its a read_only message, only allow user.destroy and don't send the message
			logger.info("READ ONLY: "+read_only+type+action);
			if(read_only){
				if(!(type.equals("user") && action.equals("destroy")))
					return new JSONObject();
				(new UserFilter()).destroy(this);
				return new JSONObject();
			}
			Timey timer = new Timey();
			JSONObject obj = null;
		try{
			Class<?> c = Class.forName("jupiter.filters."+capitalize(type)+"Filter");
			Object t = c.newInstance();
			Method m = c.getDeclaredMethod(action, this.getClass());
			obj = (JSONObject) m.invoke(t, this);
		}catch(ClassNotFoundException e){
			logger.debug("Class not found: jupiter.filters."+capitalize(type)+"Filter", e);
			return null;
		} catch (InstantiationException e) {
			logger.debug("InstantiationException", e);
			return null;
		} catch (IllegalAccessException e) {
			logger.debug("IllegalAccessException", e);
			return null;
		} catch (SecurityException e) {
			logger.debug("SecurityException", e);
			return null;
		} catch (NoSuchMethodException e) {
			logger.debug("NoSuchMethodException", e);
			return null;
		} catch (IllegalArgumentException e) {
			logger.debug("IllegalArgumentException", e);
			return null;
		} catch (InvocationTargetException e) {
			logger.debug("InvocationTargetException", e);
			return null;
		} finally {
			timer.end("Filter"+capitalize(type)+action);
			return obj;
		}
	}
	public void putInInboxes(){
		Timey timer = new Timey();
		boolean canceled;
		try{
			canceled = parsed.getBoolean("cancelInbox");
		}catch(Exception e){
			canceled = false;
		}
		if(canceled == true) return;
		if(to == null){
			jupiter.messaging.ApplicationAddressBook.addToInboxesForDomain(this);
		}else{
			jupiter.messaging.ApplicationAddressBook.addToOneInbox(this);
		}
		timer.end("SentToInboxes");
	}
	public String to(){
		return to;
	}
	public void banInbox(boolean ban){
		jupiter.messaging.ApplicationAddressBook.banInbox(this, ban);
	}
	public String domainName(){
		String domain_name = "jabbify.com";
		Matcher matcher = domainp.matcher(page);
		boolean matchFound = matcher.find();
		if(matchFound){
			domain_name = matcher.group(1).toLowerCase();
		}else{
			domain_name = "file.file";
		}
		return domain_name;
	}
	public String toString(){
		return parsed.toString();
	}
	static public EventSubscriber subscriber = new EventSubscriber();
	
	public static String capitalize(String s) {
        if (s.length() == 0) return s;
        return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
    }
	public void stillSend(){
		stillSend = true;
	}
	public void ban(){
		is_banned = true;
	}
	public void register(){
		is_registered = true;
	}
	public HttpServletRequest getRequest(){
		return request;
	}
	public void cancelSendingToInboxes(){
		try{
			parsed.put("cancelInbox", true);
		}catch(JSONException e){
			logger.error("JSONException", e);
		}
		
	}
	public String getPageFromKey(String key){
		String page = null;
		Connection conn = null;
	    Statement stmt = null;
	    ResultSet rs = null;
		try{
			conn = DBConnection.ds.getConnection();
			stmt = conn.createStatement();
			rs = stmt.executeQuery("SELECT d.name FROM domains d WHERE d.key='"+key+"'");
			while (rs.next()) {
				page = "http://"+rs.getString("d.name");
	        }
	        rs.close();
	        stmt.close();
	        conn.close();
			return page;
		}catch(SQLException e){
			try{
		        rs.close();
		        stmt.close();
		        conn.close();
	        } catch(SQLException close_e){
				logger.error("SQLException: ERROR in closing connections", close_e);
	        }
			logger.error("SQLException", e);
			return null;
		}
	}
}
